package org.meb.lotrlcg.db.dao;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.SingularAttribute;

import org.apache.commons.lang.StringUtils;
import org.meb.lotrlcg.db.model.Card;
import org.meb.lotrlcg.db.model.CardType;
import org.meb.lotrlcg.db.model.Card_;
import org.meb.lotrlcg.db.model.EncounterSetBase;
import org.meb.lotrlcg.db.model.EncounterSetBase_;
import org.meb.lotrlcg.db.model.ScenEnstLink;
import org.meb.lotrlcg.db.model.ScenEnstLink_;
import org.meb.lotrlcg.db.model.ScenarioBase;
import org.meb.lotrlcg.db.model.ScenarioBase_;
import org.meb.lotrlcg.db.model.Sphere;
import org.meb.lotrlcg.db.query.CardQuery;
import org.meb.lotrlcg.db.query.Sorting.Direction;
import org.meb.lotrlcg.db.query.Sorting.Item;

public class CardDao {

	private EntityManager em;

	public CardDao(EntityManager em) {
		this.em = em;
	}

	private TypedQuery<Card> createQuery(Card example) {
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Card> cq = cb.createQuery(Card.class);
		Root<Card> root = cq.from(Card.class);
		cq.select(root);

		List<Predicate> where = new ArrayList<Predicate>();

		String name = StringUtils.trimToEmpty(example.getName()).toLowerCase();
		if (StringUtils.isNotBlank(name)) {
			where.add(cb.like(cb.lower(root.get(Card_.name)), name));
		}

		CardType typeCode = example.getTypeCode();
		if (typeCode != null) {
			where.add(cb.equal(root.get(Card_.typeCode), typeCode));
		}

		String typeDisplay = StringUtils.trimToEmpty(example.getTypeDisplay()).toLowerCase();
		if (StringUtils.isNotBlank(typeDisplay)) {
			where.add(cb.like(cb.lower(root.get(Card_.typeDisplay)), typeDisplay));
		}

		String sphereDisplay = StringUtils.trimToEmpty(example.getSphereDisplay()).toLowerCase();
		if (StringUtils.isNotBlank(sphereDisplay)) {
			where.add(cb.like(cb.lower(root.get(Card_.sphereDisplay)), sphereDisplay));
		}

		String crstName = StringUtils.trimToEmpty(example.getCrstName()).toLowerCase();
		if (StringUtils.isNotBlank(crstName)) {
			where.add(cb.like(cb.lower(root.get(Card_.crstName)), crstName));
		}

		String crstTechName = StringUtils.trimToEmpty(example.getCrstTechName()).toLowerCase();
		if (StringUtils.isNotBlank(crstTechName)) {
			where.add(cb.equal(cb.lower(root.get(Card_.crstTechName)), crstTechName));
		}

		Integer number = example.getNumber();
		if (number != null) {
			where.add(cb.equal(root.get(Card_.number), number));
		}

		Long id = example.getId();
		if (id != null) {
			where.add(cb.equal(root.get(Card_.id), id));
		}

		if (where.size() > 0) {
			cq.where(where.toArray(new Predicate[where.size()]));
		}
		cq.orderBy(cb.asc(root.get(Card_.name)));

		return em.createQuery(cq);
	}

	public List<Card> find(Card example) {
		return createQuery(example).getResultList();
	}

	public Card findUnique(Card example) {
		Card csl;
		try {
			csl = createQuery(example).getSingleResult();
		} catch (NoResultException e) {
			csl = null;
		}
		return csl;
	}

	public List<Card> find(CardQuery query) {
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Card> cq = cb.createQuery(Card.class);
		Root<Card> root = cq.from(Card.class);

		List<Predicate> predicates = new ArrayList<Predicate>();

		String text = StringUtils.trimToEmpty(query.getText()).toLowerCase();
		if (StringUtils.isNotBlank(text)) {
			text = "%" + text + "%";
			List<Predicate> textPredicates = new ArrayList<Predicate>();
			if (query.isSearchInName()) {
				textPredicates.add(cb.like(cb.lower(root.get(Card_.name)), text));
			}
			if (query.isSearchInTrait()) {
				textPredicates.add(cb.like(cb.lower(root.get(Card_.trait)), text));
			}
			if (query.isSearchInText()) {
				textPredicates.add(cb.like(cb.lower(root.get(Card_.text)), text));
			}

			if (textPredicates.size() > 0) {
				predicates.add(cb.or(textPredicates.toArray(new Predicate[textPredicates.size()])));
			}
		}

		List<CardType> cardTypes = query.getCardTypes();
		if (cardTypes != null && cardTypes.size() > 0) {
			predicates.add(cb.isTrue(root.get(Card_.typeCode).in(cardTypes)));
		}

		List<Sphere> spheres = query.getSpheres();
		if (spheres != null && spheres.size() > 0) {
			predicates.add(cb.isTrue(root.get(Card_.sphereCode).in(spheres)));
		}

		List<Long> cardSetIds = query.getCardSetIds();
		if (cardSetIds != null && cardSetIds.size() > 0) {
			predicates.add(cb.isTrue(root.get(Card_.crstId).in(cardSetIds)));
		}

		List<Long> encounterSetIds = query.getEncounterSetIds();
		if (encounterSetIds != null && encounterSetIds.size() > 0) {
			predicates.add(cb.isTrue(root.get(Card_.enstId).in(encounterSetIds)));
		}

		List<Long> scenarioIds = query.getScenarioIds();
		if (scenarioIds != null && scenarioIds.size() > 0) {
			Subquery<Long> sq = cq.subquery(Long.class);
			Root<ScenarioBase> sqScenarioBase = sq.from(ScenarioBase.class);
			Join<ScenEnstLink, EncounterSetBase> sqEncounterSetBase = sqScenarioBase.join(
					ScenarioBase_.scenEnstLinkItems).join(ScenEnstLink_.encounterSetBase);
			sq.select(sqEncounterSetBase.get(EncounterSetBase_.id));
			sq.where(cb.isTrue(sqScenarioBase.get(ScenarioBase_.id).in(scenarioIds)));

			predicates.add(cb.in(root.get(Card_.enstId)).value(sq));
		}

		Boolean unique = query.getUnique();
		if (unique != null) {
			predicates.add(cb.equal(root.get(Card_.unique), unique));
		}

		Boolean localized = query.getLocalized();
		if (localized != null) {
			predicates.add(cb.equal(root.get(Card_.localized), localized));
		}

		Boolean corrected = query.getCorrected();
		if (corrected != null) {
			if (corrected.booleanValue()) {
				predicates.add(root.get(Card_.faqText).isNotNull());
			} else {
				predicates.add(root.get(Card_.faqText).isNull());
			}
		}

		Collection<Item> items = query.getSorting().items();
		if (items != null && items.size() > 0) {
			ManagedType<Card> mt = em.getMetamodel().managedType(Card.class);
			List<Order> orderList = new ArrayList<Order>();
			for (Item item : items) {
				SingularAttribute<? super Card, ?> attribute = mt.getSingularAttribute(item
						.getProperty());
				Path<?> path = root.get(attribute);
				if (item.getDirection() == Direction.ASC) {
					orderList.add(cb.asc(path));
				} else if (item.getDirection() == Direction.DESC) {
					orderList.add(cb.desc(path));
				}
			}
			cq.orderBy(orderList);
		}

		cq.select(root).where(predicates.toArray(new Predicate[predicates.size()]));
		return em.createQuery(cq).getResultList();
	}
}
